# -*- coding: utf-8 -*-
"""
@Time : 2024/7/13 21:35
@Auth : Junjie Duan
@File : canvas_app.py
    -i https://pypi.tuna.tsinghua.edu.cn/simple
    https://emojipedia.org/
    https://streamlit-emoji-shortcodes-streamlit-app-gwckff.streamlit.app/
"""

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/4/11 16:07
# @File    : canvas_app.py
# @Description : -i http://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn

import tkinter as tk
import ttkbootstrap as ttk
from tkinter import Canvas
from ttkbootstrap.dialogs import Messagebox


class RelationLine:

    def __init__(self, canvas: Canvas, start: tuple = None, end: tuple = None, name=None, from_id=None,
                 to_id=None, offset=None):
        """

            关系线存在时 开始位置结束位置必须存
        :param canvas: 画板
        :param start:  开始位置
        :param end: 结束位置
        :param name: 线名称
        :param from_id: 起始点ID
        :param to_id: 结束点ID
        :param offset: 偏移量
        """
        self.start = start
        self.end = end
        self.name = name
        self.from_id = from_id
        self.to_id = to_id
        self.offset = offset
        self.canvas = canvas
        self.draw_complete = False

    def update_start_point(self, x, y):
        self.start = (x, y)
        return self

    def update_end_point(self, x, y):
        self.end = (x, y)
        return self

    @staticmethod
    def get_bezier(p1, p2, offset=None) -> list:
        if offset is None:
            offset = abs(p2[0] - p1[0]) / 2
        # 两个控制点位置
        c1, c2 = (p1[0] + offset, p1[1]), (p2[0] - offset, p2[1])
        return [p1[0], p1[1], c1[0], c1[1], c2[0], c2[1], p2[0], p2[1]]

    def drawing(self):
        # 绘制
        if self.draw_complete:
            self.redraw()
        else:
            bezier_line = RelationLine.get_bezier(self.start, self.end)
            self.canvas.create_line(bezier_line, tags=self.name, arrow="last", fill="blue", width=2, smooth="True")

    def redraw(self):
        # 重新绘制
        if self.draw_complete:
            self.canvas.delete(self.name)

        bezier_line = RelationLine.get_bezier(self.start, self.end)
        self.draw_complete = self.canvas.create_line(bezier_line, tags=self.name, arrow="last", fill="blue", width=2,
                                                     smooth="True")

    def set_from_id(self, from_id):
        self.from_id = from_id

    def set_to_id(self, to_id):
        self.to_id = to_id

    def re_name(self, name):
        self.name = name


class GridCanvas(ttk.Canvas):
    """"""

    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)

        self.relations = {}
        self.draggableFrames = {}
        self.current_relation: RelationLine = None
        self.current_draggableFrames = None

    def build(self) -> None:
        """"""
        bg = "#1F1F1F"
        self.configure(bg=bg, highlightthickness=0)
        self.pack(fill="both", expand=True, anchor="center")

        for x in range(-5, 2565, 16):
            self.create_line(x, -5, x, 1605, fill="grey")

        for y in range(-5, 1605, 16):
            self.create_line(-5, y, 2565, y, fill="grey")

    def set_relation(self, relation: RelationLine):

        """
        :param relation:
        :return: 返回一个新的 relation name 作为字典的Key
        """
        # 检擦一下 开始节点 是否将其加入name中,  如果两个节点已经有 关联了怎么办
        from_id, to_id = relation.from_id, relation.to_id
        relation_name = relation.name
        key = f"{relation_name}_{from_id}_{to_id}"

        if self.check_relation_binding(from_id, to_id):
            print("不允许 两者重复绑定")
            self.current_relation = None
            return None

        from_frame = self.get_widget_by_id(from_id)
        # to_frame = self.nametowidget(to_name)
        if key not in from_frame.with_me_relations:
            print("Output Point Add Name(with_me):", key)
            from_frame.with_me_relations.add(key)

        # 记录到集合
        self.relations[key] = relation
        return key

    def get_relation(self, key) -> RelationLine:
        return self.relations.get(key)

    def set_frame(self, key, relation: RelationLine):
        self.draggableFrames[key] = relation

    def get_frame(self, key):
        return self.draggableFrames.get(key)

    def check_relation_binding(self, from_id, to_id):
        """
            检擦两者是否重复绑定
        :param from_id:
        :param to_id:
        :return:
        """
        for widget in self.winfo_children():
            if isinstance(widget, RelationLine):
                if widget.from_id == from_id and widget.to_id == to_id:
                    return True
        return False

    def get_widget_by_id(self, winfo_id) -> RelationLine:
        for widget in self.winfo_children():
            if widget.winfo_id() == winfo_id:
                return widget

    def get_widget_by_name(self, winfo_name):
        return self.nametowidget(winfo_name)


class DraggableFrame(ttk.Frame):

    def __init__(self, master: GridCanvas = None, **kwargs):

        super().__init__(master, **kwargs)
        self.master: GridCanvas = master
        # 事件参考:https://blog.csdn.net/littlefun591/article/details/139684063
        # 左键点击
        self.bind("<Button-1>", self.on_start_drag)

        # 双击
        self.bind("<Double-1>", self.double_click)

        # 右键右键
        self.bind("<Button-3>", self.popup)  # 右键事件

        # 点击左键移动
        self.bind("<B1-Motion>", self.on_dragging)
        # 松开左键
        self.bind("<ButtonRelease-1>", self.on_stop_drag)
        # 右键松开
        self.bind("<ButtonRelease-3>", lambda x: print("右键松开了"))
        # 鼠标滑过
        self.bind("<Enter>", self.on_enter)
        # 鼠标离开
        self.bind("<Leave>", self.on_leave)

        # 鼠标滚轮按下
        self.bind("<Button-2>", lambda x: print("滚轮按下"))
        self.bind("<ButtonRelease-2>", lambda x: print("滚轮松开"))

        # 滚轮滑动
        self.bind("<MouseWheel>", lambda x: print("滚轮:", x))

        self._drag_data = {"x": 0, "y": 0}
        # 定义一个全局变量来跟踪鼠标是否正在标签上滑过
        self.is_hovering = False

        # 关系线
        """
            关系线
            with_me: 谁以我为 开头
            with_him: 谁以我为 结尾
        """
        self.with_me_relations = set()
        self.with_him_relations = set()

        self.input_point = tk.Frame(self, width=10, height=10)
        self.output_point = tk.Frame(self, width=10, height=10)

        self.input_point.place(x=0, y=0)
        self.output_point.place(x=90, y=40)

        self.output_point.bind("<Button-1>", self.on_out_point)
        self.output_point.bind("<B1-Motion>", self.on_out_point_dragging)
        self.output_point.bind("<ButtonRelease-1>", self.up_leave_output_point)

        self.input_point.bind("<Enter>", self.input_point_enter)
        # 鼠标离开
        self.input_point.bind("<Leave>", self.input_point_leave)

    def on_out_point(self, event):
        """ 左键点击出口按钮 """
        # print("==========================")
        # print("envet:", event.x, ", ", event.y)
        # print(" self:", self.winfo_width(), self.output_point.winfo_width())
        # frame尺寸 - out_point的尺寸 + out_point上的鼠标的位置 = frame上的位置
        delta_x = self.winfo_width() - self.output_point.winfo_width() + event.x
        delta_y = self.winfo_height() - self.output_point.winfo_height() + event.y

        x = self.winfo_x() + delta_x
        y = self.winfo_y() + delta_y

        # print("on-out-point:", x, y)
        relation_start = RelationLine(self.master, start=(x, y),
                                      name=f"{self.winfo_name()}_{len(self.with_me_relations)}",
                                      from_id=self.winfo_id())
        # 创建临时线
        self.master.current_relation = relation_start

    def on_out_point_dragging(self, event):
        """ 点击出口按按钮后滑动鼠标 """
        # 画线到当前位置
        start_point = self.master.current_relation.start
        # print("out-point-dragging:", event.x, event.y)
        # 鼠标移动的终点位置 - 鼠标点击的起始位置
        x = event.x + start_point[0]
        y = event.y + start_point[1]
        # print(x, y)
        self.master.current_relation.update_end_point(x, y).redraw()

    def up_leave_output_point(self, event):
        """ 抬起出口按钮左键 """
        print("on-leave-output-point: 抬起离开了")
        # 鼠标抬起时判断是否进入(to_id  不为空标识已添加到顶一个入口) 另一个frame的入口 如果没有将清空 current_relation
        current_relation = self.master.current_relation
        #
        relation_name = current_relation.name
        to_id = current_relation.to_id

        # 其实这个时候不管是不是 放入了另一个frame 都要置空, 不为空时表示 创建的节点链接到了其他的frame 将其保存到 with me
        if to_id is not None:
            self.master.current_relation = None
            print("Output Point Add Name(with_me):", relation_name)
            self.with_me_relations.add(relation_name)

    def input_point_enter(self, event):
        """ 鼠标划过 """
        print("input-point-leave:鼠标进来了")
        relation = self.master.current_relation
        if relation is not None:
            pass

    def input_point_leave(self, event):
        """ 鼠标离开 """
        print("input-point-leave:鼠标离开了")
        relation = self.master.current_relation
        if relation is not None:
            relation.set_to_id(self.winfo_id())
            # set_relation 会生成一个 relation key  返回None时 表示两者已有关联关系
            key = self.master.set_relation(relation)
            if key is not None:
                print("Input Point Add Name(with_him):", key)
                self.with_him_relations.add(key)
                self.master.current_relation = None

    def on_start_drag(self, event):
        #  将当前元素放在最上层
        self.lift()
        # 放到底层 有个问题是 他放进了最底层
        # self.lower()
        print("鼠标点击Frame：", event.x, event.y)
        # 鼠标点击时的位置(当前frame上的)
        self._drag_data["x"] = event.x
        self._drag_data["y"] = event.y

    def on_enter(self, event):
        # 鼠标进入时改变背景色
        self.is_hovering = True
        print("鼠标进来了")

        # 安排一个回调函数在1秒后执行，以恢复背景色
        self.after(1000, self.on_leave)

    def on_leave(self, event=None):
        self.is_hovering = False
        # 鼠标离开时恢复背景色
        print("鼠标离开....")

    def double_click(self, event):
        print("左键双击")

    def popup(self, event):
        # 创建一个弹出菜单
        popup_menu = tk.Menu(self, tearoff=0)
        popup_menu.add_command(label="信息", command=lambda: self.info())
        popup_menu.add_command(label="置底", command=self.to_lower)
        popup_menu.add_command(label="删除", command=lambda: self.delete())
        # 显示弹出菜单
        popup_menu.tk_popup(event.x_root, event.y_root)

    def to_lower(self):
        # lower 将当前元素放到底层 有个问题是 他放进了最底层 而我们希望他是在master下的最底层 而不是root下
        # self.lower()
        self.lower()
        # self.lift(self.master)

    def info(self):
        info = {
            'class': self.winfo_class(),
            'id': self.winfo_id(),
            'name': self.winfo_name(),
            'parent': self.winfo_parent(),
            'x': self.winfo_x(),
            'y': self.winfo_y(),
            'width': self.winfo_width(),
            'height': self.winfo_height(),
            'config': self.config()  # 获取所有配置选项和它们的值
        }
        Messagebox.ok(info.__str__(), position=(500, 500))
        print(info)

    def delete(self):
        print("删除事件")
        self.destroy()

    def clear_all_widgets(self):
        print("删除当前frame下所有子组件")
        for widget in self.winfo_children():
            widget.destroy()

    def on_dragging(self, event):

        # 鼠标移动的终点位置 - 鼠标点击的起始位置
        delta_x = event.x - self._drag_data["x"]
        delta_y = event.y - self._drag_data["y"]

        # winfo_x,winfo_y 当前frame的位置
        x = self.winfo_x() + delta_x
        y = self.winfo_y() + delta_y

        print("-------------------")
        # 画板的尺寸
        parent_width = self.master.winfo_width()
        parent_height = self.master.winfo_height()

        # 当前frame的尺寸
        current_frame_width = self.winfo_width()
        current_frame_height = self.winfo_height()
        print(f"current frame size ({current_frame_width},{current_frame_height})")
        print(f"parent Frame Width: {parent_width} pixels Height: {parent_height} pixels")
        # 最大不可 超出frame最大高度 最少暴漏出10个像素点
        x = x if parent_width > x else parent_width - 10
        y = y if parent_height > y else parent_height - 10
        x = -current_frame_width + 10 if x <= -current_frame_width + 10 else x
        y = -current_frame_height + 10 if y <= -current_frame_height + 10 else y
        print(f"move to ({x},{y})")

        # 移动frame
        self.place(x=x, y=y)

        """
            关系线
            with_me: 谁以我为 开头
            with_him: 谁以我为 结尾
        """
        for name in self.with_him_relations:
            print("With Him Relations Name :" + name)
            x_ = x + (self.output_point.winfo_width() / 2)
            y_ = y + (self.output_point.winfo_height() / 2)
            self.master.get_relation(name).update_end_point(x_, y_).redraw()

        for name in self.with_me_relations:
            print("With Me Relations Name :" + name)
            # 计算出口位置
            x_ = x + self.winfo_width() - (self.output_point.winfo_width() / 2)
            y_ = y + self.winfo_height() - (self.output_point.winfo_height() / 2)
            self.master.get_relation(name).update_start_point(x_, y_).redraw()

    def on_stop_drag(self, event):
        print("停止移动")
        pass  # 可以在这里添加停止拖动时的处理逻辑


class Body(ttk.Frame):

    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)

        self.master = master
        self.pack(fill=ttk.BOTH, expand=ttk.YES)
        # 头部
        self.top = ttk.Frame(self, bootstyle="primary", height=50)
        # self.top.pack(fill=ttk.X, side=ttk.TOP)
        # 内容
        self.div = ttk.Frame(self, bootstyle="warning")
        self.div.pack(fill=ttk.BOTH, expand=ttk.YES, side=ttk.TOP)
        # 底部
        self.bottom = ttk.Frame(self, bootstyle="light", height=20)
        # self.bottom.pack(fill=ttk.X, side=ttk.TOP)

        # 内容扩展
        self.tab_control = ttk.Frame(self.div, bootstyle="secondary", width=50)
        # self.tab_control.pack(fill=ttk.Y, side=ttk.LEFT)

        self.body = ttk.Panedwindow(self.div, orient='horizontal')
        self.body.pack(fill=ttk.BOTH, expand=ttk.YES, side=ttk.LEFT)

        self.sidebar = ttk.Frame(self.body, bootstyle="success", width=100)
        # self.sidebar.pack(fill=ttk.Y, side=ttk.LEFT)

        self.body_canvas = ttk.Frame(self.body, bootstyle="dark")
        # body_canvas的子集 用于添加网格画布
        self.grid_canvas = None
        self.body_canvas.pack(fill=ttk.BOTH, expand=ttk.YES, side=ttk.LEFT)

        # self.body.add(self.sidebar)
        self.body.add(self.body_canvas)

        self.init_body()

    def init_body(self):

        self.grid_canvas = GridCanvas(self.body_canvas)
        self.grid_canvas.build()

        draggable_frame = DraggableFrame(self.grid_canvas, width=100, height=50, bootstyle='light', name="白色")
        draggable_frame.place(x=50, y=50)

        draggable_frame2 = DraggableFrame(self.grid_canvas, width=100, height=50, bootstyle='danger', name="红色")
        draggable_frame2.place(x=200, y=50)

        draggable_frame2 = DraggableFrame(self.grid_canvas, width=100, height=50, bootstyle='dark', name="黑色")
        draggable_frame2.place(x=350, y=50)

        print("==============测试 根据 name 或 id 查找子部件=================")
        try:
            temp_id, temp_name = None, None
            for widget in self.grid_canvas.winfo_children():
                print(widget.winfo_name(), widget.winfo_id())
                temp_id, temp_name = widget.winfo_id(), widget.winfo_name()

            print("根据name查找")
            temp = self.grid_canvas.nametowidget(temp_name)
            print(temp)
            # 这里没有提供根据ID直接查到对应的子集 不过可以直接遍历 子部件 一个一个对比查询
        except Exception as e:
            pass
        print("====================== 测试结束 ============================")


if __name__ == '__main__':
    root = tk.Tk()
    root_width = 600
    root_height = 300
    root_x, root_y = 500, 200
    root.geometry(f"{root_width}x{root_height}+{root_x}+{root_y}")

    # 去掉边框以及关闭按钮等 不可逆
    # root.overrideredirect(True)

    # 透明度
    # root.attributes('-alpha', 0.7)

    Body(root)
    root.mainloop()

